Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Button.disabled can be a tooltip #202

Merged
merged 6 commits into from
Aug 4, 2021

Conversation

stephenh
Copy link
Contributor

@stephenh stephenh commented Aug 4, 2021

This is a) a blatant copy/paste from one of @DeanGilewicz 's WIP PRs + b) a long-standing need to be able to s/BP Button/Beam Button in BP, because we have quite a few places that use current BP Button's disabledReason prop.

Copy link
Contributor

@chr1sjf0x chr1sjf0x left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍣

Comment on lines 12 to 14
disabled?: boolean;
/** If set, will show a tooltip about why the button is disabled. */
disabledReason?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW: I had been musing with @KoltonG about whether it would be possible/good to have semi-direct integration with potential operations. Like:

Suggested change
disabled?: boolean;
/** If set, will show a tooltip about why the button is disabled. */
disabledReason?: string;
/** If a potential operation is provided, will show a tooltip about why the button is disabled when the operation is not allowed. */
disabled?: boolean | PotentialOperation;

I think whether this is a good idea is questionable, since it would directly tie the tooltip content to the potential operation disabled reasons... when in most cases you'd probably want some additional processing of those reasons 🤔

🤷

Copy link
Contributor Author

@stephenh stephenh Aug 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm... yeah, that would be pretty cute, would be a easy if the reasons were just strings:

type PotentialOperation2 {
  allowed: Boolean!
  disabledReasons: [Reason!]!
}

type Reason {
  message: String!
  entity: HasBlueprintUrl
}

interface HasBlueprintUrl {
  blueprintUrl: Url!
}

But since they're this complex Reason type, with pretty bespoke Blueprint-y things in it, yeah I'm not sure (as you already mentioned) we'd want to leak that directly into Beam...

Maybe if it was:

disabled: boolean | ReactNode

Where if disabled is ever "not a boolean" / i.e. a ReactNode (which could be just a string, or a complex structure) it knows that means "put the react node in the tooltip".

And then we had an adapter like:

<Button disabled=renderPotentialOperation(query.canDoFoo) />

Where the helper method would do app-specific rendering of the PotentialOperation2 instead "just some react nodes to put into the tooltip"...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chr1sjf0x / @KoltonG pushed up another version that does:

disabled: boolean | ReactNode

Wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍

Copy link
Contributor

@KoltonG KoltonG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Will be nice to have this built in!

@@ -130,3 +130,7 @@ export function Buttons(args: ButtonProps) {
</div>
);
}

export function ButtonWithTooltip() {
return <Button disabled={true} disabledReason="You cannot currently perform this operation." label="Upload" />;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts: On having the disabled prop take in a boolean or a string for the message? Just thinking that disabled and disabledReason would always be in sync.


export function TooltipDisabled() {
return (
<Tooltip title="Tooltip Info" disabled={true}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! I never knew a Tooltip could be disabled!

@@ -10,27 +10,30 @@ import { Css } from "src/Css";

interface TooltipProps {
/** The content that shows up when hovered */
title: string;
title: ReactNode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! @kbesingeryeh the fix got in!

state,
triggerRef,
);
const { tooltipProps } = useTooltip(_tooltipProps, state);

return (
<>
{React.cloneElement(children, { ref: triggerRef, ...triggerProps })}
<span ref={triggerRef} {...triggerProps}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Any advantage to adding a wrapping span?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure cloneElement ignores the ref prop (both ref and key are ignored, and the children's original ref/key are still used). I'm not really sure if this fixes anything per se, I'd just noticed that "cloneElement doesn't support ref" while doing other things.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that cloneElement supports refs otherwise the Tooltip wouldn't work (maybe its ignored if it is given) 😢 Are you facing a case that you need to pass a ref to the Tooltip component (that might be why, since it's being overwritten)?

Here's an old conversation on the subject facebook/react#8873 (comment)

@oagboola and I approached it this way to reduce the wrapping elements needed for the Tooltip and add the Popper ref to the child element (assuming that the child would not have a ref of its own). All good either way!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just looking at:

https://reactjs.org/docs/react-api.html#cloneelement

"Clone and return a new React element using element as the starting point. The resulting element will have the original element’s props with the new props merged in shallowly. New children will replace existing children. key and ref from the original element will be preserved."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's that preserved behaviour I think is what is special - like if theres are no overrides then it will keep it the original ref. I'll CodeSandbox this for fun!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a CodeSandbox showing that the children's ref is overridden by the clone elements ref! Still a very interesting result!

https://codesandbox.io/s/reactcloneelement-ref-override-gyyhm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see, it "preserves it" as in "it won't drop the existing one", but you're allowed to override it...playing devils advocate, if the caller did want to use the ref, i.e. the parentRef in your example, this approach drops it?

I'll put back though, given it's not broken as I originally though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also makes me think maybe we should have a few snapshot tests to show all of the aria-* stuff getting wired together. Between this change, and others, I'm not really sure when aria attributes are/are not getting hooked up, and that isn't covered by storybook.

*
* If a ReactNode, it's treated as a "disabled reason" that's shown in a tooltip.
*/
disabled?: boolean | ReactNode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

@stephenh stephenh changed the title feat: Add Button.disabledReason. feat: Add Button.disabled can be a tooltip Aug 4, 2021
@stephenh stephenh merged commit 84b4641 into main Aug 4, 2021
@stephenh stephenh deleted the add-button-disabled-reason branch August 4, 2021 21:05
homebound-team-bot pushed a commit that referenced this pull request Aug 4, 2021
## [2.37.0](v2.36.0...v2.37.0) (2021-08-04)

### Features

* Add Button.disabled can be a tooltip ([#202](#202)) ([84b4641](84b4641))
@homebound-team-bot
Copy link

🎉 This PR is included in version 2.37.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants